//package cn.oyzh.jeditermfx.terminal.model;
//
//import cn.oyzh.common.log.JulLog;
//import cn.oyzh.jeditermfx.terminal.StyledTextConsumer;
//import cn.oyzh.jeditermfx.terminal.TextStyle;
//import cn.oyzh.jeditermfx.terminal.util.CharUtils;
//import kotlin.Pair;
//import org.jetbrains.annotations.NotNull;
//import org.jetbrains.annotations.Nullable;
//
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.Collections;
//import java.util.Iterator;
//import java.util.List;
//import java.util.concurrent.CopyOnWriteArrayList;
//import java.util.concurrent.atomic.AtomicInteger;
//import java.util.function.Consumer;
//import java.util.stream.Collectors;
//
///**
// * @author traff
// */
//public final class TerminalLine {
//
//    private TextEntries myTextEntries = new TextEntries();
//    private boolean myWrapped = false;
//    private final List<TerminalLineIntervalHighlighting> myCustomHighlightings = new CopyOnWriteArrayList<>();
//    private final AtomicInteger myModificationCount = new AtomicInteger(0);
//    TerminalLine myTypeAheadLine;
//
//    public TerminalLine() {
//    }
//
//    public TerminalLine(@NotNull TextEntry entry) {
//        myTextEntries.add(entry);
//    }
//
//    public static TerminalLine createEmpty() {
//        return new TerminalLine();
//    }
//
//    public @NotNull String getText() {
//        StringBuilder result = new StringBuilder(myTextEntries.myLength);
//        for (TerminalLine.TextEntry textEntry : myTextEntries) {
//            // NUL can only be at the end
//            if (textEntry.getText().isNul()) {
//                break;
//            }
//            result.append(textEntry.getText());
//        }
//        return result.toString();
//    }
//
//    public @NotNull TerminalLine copy() {
//        TerminalLine result = new TerminalLine();
//        for (TextEntry entry : myTextEntries) {
//            result.myTextEntries.add(entry);
//        }
//        result.myWrapped = myWrapped;
//        return result;
//    }
//
//    public char charAt(int x) {
//        TerminalLine typeAheadLine = myTypeAheadLine;
//        if (typeAheadLine != null) {
//            return typeAheadLine.charAt(x);
//        }
//        String text = getText();
//        return x < text.length() ? text.charAt(x) : CharUtils.EMPTY_CHAR;
//    }
//
//    /**
//     * @return total length of text entries.
//     */
//    public int length() {
//        return myTextEntries.length();
//    }
//
//    public boolean isWrapped() {
//        return myWrapped;
//    }
//
//    public void setWrapped(boolean wrapped) {
//        myWrapped = wrapped;
//    }
//
//    public void clear(@NotNull TextEntry filler) {
//        myTextEntries.clear();
//        myTextEntries.add(filler);
//    }
//
//    public void writeString(int x, @NotNull CharBuffer str, @NotNull TextStyle style) {
//        writeCharacters(x, style, str);
//    }
//
//    public void insertString(int x, @NotNull CharBuffer str, @NotNull TextStyle style) {
//        insertCharacters(x, style, str);
//    }
//
//    private void writeCharacters(int x, @NotNull TextStyle style, @NotNull CharBuffer characters) {
//        int len = myTextEntries.length();
//
//        if (x >= len) {
//            // fill the gap
//            if (x - len > 0) {
//                myTextEntries.add(new TextEntry(TextStyle.EMPTY, new CharBuffer(CharUtils.NUL_CHAR, x - len)));
//            }
//            myTextEntries.add(new TextEntry(style, characters));
//        } else {
//            len = Math.max(len, x + characters.length());
//            myTextEntries = merge(x, characters, style, myTextEntries, len);
//        }
//    }
//
//    private void insertCharacters(int x, @NotNull TextStyle style, @NotNull CharBuffer characters) {
//        int length = myTextEntries.length();
//        if (x > length) {
//            writeCharacters(x, style, characters);
//            return;
//        }
//
//        Pair<char[], TextStyle[]> pair = toBuf(myTextEntries, length + characters.length());
//
//        for (int i = length - 1; i >= x; i--) {
//            pair.getFirst()[i + characters.length()] = pair.getFirst()[i];
//            pair.getSecond()[i + characters.length()] = pair.getSecond()[i];
//        }
//        for (int i = 0; i < characters.length(); i++) {
//            pair.getFirst()[i + x] = characters.charAt(i);
//            pair.getSecond()[i + x] = style;
//        }
//        myTextEntries = collectFromBuffer(pair.getFirst(), pair.getSecond());
//    }
//
//    private static TextEntries merge(int x, @NotNull CharBuffer str, @NotNull TextStyle style, @NotNull TextEntries entries, int lineLength) {
//        Pair<char[], TextStyle[]> pair = toBuf(entries, lineLength);
//
//        for (int i = 0; i < str.length(); i++) {
//            pair.getFirst()[i + x] = str.charAt(i);
//            pair.getSecond()[i + x] = style;
//        }
//
//        return collectFromBuffer(pair.getFirst(), pair.getSecond());
//    }
//
//    private static Pair<char[], TextStyle[]> toBuf(TextEntries entries, int lineLength) {
//        Pair<char[], TextStyle[]> pair = new Pair<>(new char[lineLength], new TextStyle[lineLength]);
//
//
//        int p = 0;
//        for (TextEntry entry : entries) {
//            for (int i = 0; i < entry.getLength(); i++) {
//                pair.getFirst()[p + i] = entry.getText().charAt(i);
//                pair.getSecond()[p + i] = entry.getStyle();
//            }
//            p += entry.getLength();
//        }
//        return pair;
//    }
//
//    private static TextEntries collectFromBuffer(char[] buf, @NotNull TextStyle[] styles) {
//        TextEntries result = new TextEntries();
//
//        TextStyle curStyle = styles[0];
//        int start = 0;
//
//        for (int i = 1; i < buf.length; i++) {
//            if (styles[i] != curStyle) {
//                result.add(new TextEntry(curStyle, new CharBuffer(buf, start, i - start)));
//                curStyle = styles[i];
//                start = i;
//            }
//        }
//
//        result.add(new TextEntry(curStyle, new CharBuffer(buf, start, buf.length - start)));
//
//        return result;
//    }
//
//    public void deleteCharacters(int x) {
//        deleteCharacters(x, TextStyle.EMPTY);
//    }
//
//    public void deleteCharacters(int x, @NotNull TextStyle style) {
//        deleteCharacters(x, myTextEntries.length() - x, style);
//    }
//
//    public void deleteCharacters(int x, int count, @NotNull TextStyle style) {
//        int p = 0;
//        TextEntries newEntries = new TextEntries();
//
//        int remaining = count;
//
//        for (TextEntry entry : myTextEntries) {
//            if (remaining == 0) {
//                newEntries.add(entry);
//                continue;
//            }
//            int len = entry.getLength();
//            if (p + len <= x) {
//                p += len;
//                newEntries.add(entry);
//                continue;
//            }
//            int dx = x - p; //>=0
//            if (dx > 0) {
//                //part of entry before x
//                newEntries.add(new TextEntry(entry.getStyle(), entry.getText().subBuffer(0, dx)));
//                p = x;
//            }
//            if (dx + remaining < len) {
//                //part that left after deleting count
//                newEntries.add(new TextEntry(entry.getStyle(), entry.getText().subBuffer(dx + remaining, len - (dx + remaining))));
//                remaining = 0;
//            } else {
//                remaining -= (len - dx);
//                p = x;
//            }
//        }
//        if (count > 0 && style != TextStyle.EMPTY) { // apply style to the end of the line
//            newEntries.add(new TextEntry(style, new CharBuffer(CharUtils.NUL_CHAR, count)));
//        }
//
//        myTextEntries = newEntries;
//    }
//
//    public void insertBlankCharacters(int x, int count, int maxLen, @NotNull TextStyle style) {
//        int len = myTextEntries.length();
//        len = Math.min(len + count, maxLen);
//
//        char[] buf = new char[len];
//        TextStyle[] styles = new TextStyle[len];
//
//        int p = 0;
//        for (TextEntry entry : myTextEntries) {
//            for (int i = 0; i < entry.getLength() && p < len; i++) {
//                if (p == x) {
//                    for (int j = 0; j < count && p < len; j++) {
//                        buf[p] = CharUtils.EMPTY_CHAR;
//                        styles[p] = style;
//                        p++;
//                    }
//                }
//                if (p < len) {
//                    buf[p] = entry.getText().charAt(i);
//                    styles[p] = entry.getStyle();
//                    p++;
//                }
//            }
//            if (p >= len) {
//                break;
//            }
//        }
//
//        // if not inserted yet (ie. x > len)
//        for (; p < x && p < len; p++) {
//            buf[p] = CharUtils.EMPTY_CHAR;
//            styles[p] = TextStyle.EMPTY;
//            p++;
//        }
//        for (; p < x + count && p < len; p++) {
//            buf[p] = CharUtils.EMPTY_CHAR;
//            styles[p] = style;
//            p++;
//        }
//
//        myTextEntries = collectFromBuffer(buf, styles);
//    }
//
//    public void clearArea(int leftX, int rightX, @NotNull TextStyle style) {
//        if (rightX == -1) {
//            rightX = Math.max(myTextEntries.length(), leftX);
//        }
//        writeCharacters(leftX, style, new CharBuffer(
//                rightX >= myTextEntries.length() ? CharUtils.NUL_CHAR : CharUtils.EMPTY_CHAR,
//                rightX - leftX));
//    }
//
//    public @Nullable TextStyle getStyleAt(int x) {
//        int i = 0;
//
//        for (TextEntry te : myTextEntries) {
//            if (x >= i && x < i + te.getLength()) {
//                return te.getStyle();
//            }
//            i += te.getLength();
//        }
//
//        return null;
//    }
//
//    public void process(int y, StyledTextConsumer consumer, int startRow) {
//        int x = 0;
//        int nulIndex = -1;
//        TerminalLineIntervalHighlighting highlighting = myCustomHighlightings.stream().findFirst().orElse(null);
//        TerminalLine typeAheadLine = myTypeAheadLine;
//        TextEntries textEntries = typeAheadLine != null ? typeAheadLine.myTextEntries : myTextEntries;
//        for (TextEntry te : textEntries) {
//            if (te.getText().isNul()) {
//                if (nulIndex < 0) {
//                    nulIndex = x;
//                }
//                consumer.consumeNul(x, y, nulIndex, te.getStyle(), te.getText(), startRow);
//            } else {
//                if (highlighting != null && te.getLength() > 0 && highlighting.intersectsWith(x, x + te.getLength())) {
//                    processIntersection(x, y, te, consumer, startRow, highlighting);
//                } else {
//                    consumer.consume(x, y, te.getStyle(), te.getText(), startRow);
//                }
//            }
//            x += te.getLength();
//        }
//        consumer.consumeQueue(x, y, nulIndex < 0 ? x : nulIndex, startRow);
//    }
//
//    private void processIntersection(int startTextOffset, int y, @NotNull TextEntry te, @NotNull StyledTextConsumer consumer,
//                                     int startRow, @NotNull TerminalLineIntervalHighlighting highlighting) {
//        CharBuffer text = te.getText();
//        int endTextOffset = startTextOffset + text.length();
//        int[] offsets = new int[]{startTextOffset, endTextOffset, highlighting.getStartOffset(), highlighting.getEndOffset()};
//        Arrays.sort(offsets);
//        int startTextOffsetInd = Arrays.binarySearch(offsets, startTextOffset);
//        int endTextOffsetInd = Arrays.binarySearch(offsets, endTextOffset);
//        if (startTextOffsetInd < 0 || endTextOffsetInd < 0) {
//            JulLog.error("Cannot find " + Arrays.toString(new int[]{startTextOffset, endTextOffset})
//                    + " in " + Arrays.toString(offsets) + ": " + Arrays.toString(new int[]{startTextOffsetInd, endTextOffsetInd}));
//            consumer.consume(startTextOffset, y, te.getStyle(), text, startRow);
//            return;
//        }
//        for (int i = startTextOffsetInd; i < endTextOffsetInd; i++) {
//            int length = offsets[i + 1] - offsets[i];
//            if (length == 0) continue;
//            CharBuffer subText = new SubCharBuffer(text, offsets[i] - startTextOffset, length);
//            if (highlighting.intersectsWith(offsets[i], offsets[i + 1])) {
//                consumer.consume(offsets[i], y, highlighting.mergeWith(te.getStyle()), subText, startRow);
//            } else {
//                consumer.consume(offsets[i], y, te.getStyle(), subText, startRow);
//            }
//        }
//    }
//
//    public boolean isNul() {
//        for (TextEntry e : myTextEntries) {
//            if (!e.isNul()) {
//                return false;
//            }
//        }
//
//        return true;
//    }
//
//    public boolean isEmpty() {
//        for (TextEntry e : myTextEntries) {
//            if (!e.isNul() && e.getLength() > 0) {
//                return false;
//            }
//        }
//        return true;
//    }
//
//    public boolean isNulOrEmpty() {
//        return isNul() || isEmpty();
//    }
//
//    public void forEachEntry(@NotNull Consumer<TextEntry> action) {
//        myTextEntries.forEach(action);
//    }
//
//    public @NotNull List<TextEntry> getEntries() {
//        return Collections.unmodifiableList(myTextEntries.entries());
//    }
//
//    void appendEntry(@NotNull TextEntry entry) {
//        myTextEntries.add(entry);
//    }
//
//    int getModificationCount() {
//        return myModificationCount.get();
//    }
//
//    void incrementAndGetModificationCount() {
//        myModificationCount.incrementAndGet();
//    }
//
//    @SuppressWarnings("unused") // used by IntelliJ
//    public @NotNull TerminalLineIntervalHighlighting addCustomHighlighting(int startOffset, int length, @NotNull TextStyle textStyle) {
//        TerminalLineIntervalHighlighting highlighting = new TerminalLineIntervalHighlighting(this, startOffset, length, textStyle) {
//            @Override
//            protected void doDispose() {
//                myCustomHighlightings.remove(this);
//            }
//        };
//        myCustomHighlightings.add(highlighting);
//        return highlighting;
//    }
//
//    @Override
//    public String toString() {
//        return myTextEntries.length() + " chars, " +
//                (myWrapped ? "wrapped, " : "") +
//                myTextEntries.myTextEntries.size() + " entries: " +
//                myTextEntries.myTextEntries.stream()
//                        .map(entry -> entry.getText().toString())
//                        .collect(Collectors.joining("|"));
//    }
//
//    public static class TextEntry {
//        private final TextStyle myStyle;
//        private final CharBuffer myText;
//
//        public TextEntry(@NotNull TextStyle style, @NotNull CharBuffer text) {
//            myStyle = style;
//            myText = text.clone();
//        }
//
//        public TextStyle getStyle() {
//            return myStyle;
//        }
//
//        public CharBuffer getText() {
//            return myText;
//        }
//
//        public int getLength() {
//            return myText.length();
//        }
//
//        public boolean isNul() {
//            return myText.isNul();
//        }
//
//        @Override
//        public String toString() {
//            return myText.length() + " chars, style: " + myStyle + ", text: " + myText;
//        }
//    }
//
//    private static class TextEntries implements Iterable<TextEntry> {
//        private final List<TextEntry> myTextEntries = new ArrayList<>();
//
//        private int myLength = 0;
//
//        public void add(TextEntry entry) {
//            // NUL can only be at the end of the line
//            if (!entry.getText().isNul()) {
//                for (TextEntry t : myTextEntries) {
//                    if (t.getText().isNul()) {
//                        t.getText().unNullify();
//                    }
//                }
//            }
//            myTextEntries.add(entry);
//            myLength += entry.getLength();
//        }
//
//        private List<TextEntry> entries() {
//            return myTextEntries;
//        }
//
//        @NotNull
//        public Iterator<TextEntry> iterator() {
//            return myTextEntries.iterator();
//        }
//
//        public int length() {
//            return myLength;
//        }
//
//        public void clear() {
//            myTextEntries.clear();
//            myLength = 0;
//        }
//    }
//
//    public TerminalLine getTypeAheadLine() {
//        return myTypeAheadLine;
//    }
//
//    public void setTypeAheadLine(TerminalLine myTypeAheadLine) {
//        this.myTypeAheadLine = myTypeAheadLine;
//    }
//}
